#include <ne.h>
#include <system/GlutApplication.h>

int    Width;               /* Width of window */
int    Height;              /* Height of window */
bool   mouseLeftPressed = false;
bool   mouseRightPressed = false;
bool   playAnimation = false;
int    mouseX = 0;
int    mouseY = 0;
ne::real fov = ne::Math::QUARTER_PI;

ne::Engine *pEngine = 0;
ne::GlutApplication *pApplication = 0;
ne::Camera *camera = 0;
ne::Viewport *viewport = 0;
ne::Light *light = 0;
ne::Fog fog("Fog");
ne::Timer timer;
ne::SkeletonPtr human;
ne::Joint *rootJoint, *childJoint, *leafJoint;
ne::AnimationGroupPtr animationGroup;
ne::AnimationPtr animation;
ne::Timer animationTimer;

void keyboard(unsigned char key, int x, int y);
void mouse(int button, int state, int x, int y);
void motion(int x, int y);
void loop(void);
void redraw(void);
void drawFPS(void);
void resize(int width, int height);
void init();

int main(int argc, char *argv[]) /* I - Command-line arguments */
{
    pApplication = new ne::GlutApplication();
    glutReshapeFunc(resize);
    glutDisplayFunc(loop);
    glutKeyboardFunc(keyboard);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);

    //glutFullScreen();
    pEngine = pApplication->engine;
    if (!pEngine || !pEngine->isReady())
    {
        delete pApplication;
        return 0;
    }

    init();

    glutMainLoop();

    delete pApplication;

    return 0;
}

void init()
{
    camera = new ne::Camera();
    viewport = new ne::Viewport(camera);
    camera->setPosition(ne::Vector3(0.0f, 10.0f, 30.0f));
    camera->setFarClipDistance(2000.0f);

    glClearColor(0, 0, 0, 0);
    glClearDepth(1);

    //glFrontFace(GL_CCW);
    glCullFace(GL_BACK);
    glEnable(GL_CULL_FACE);

    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    // Set multisample
    //glEnable(GL_MULTISAMPLE);
    //glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
    //glEnable(GL_SAMPLE_COVERAGE);
    //glSampleCoverage();

    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_FOG);

    glShadeModel(GL_SMOOTH);

    // Set light
    light = new ne::Light();
    light->setType(ne::LT_POINT);
    light->setAmbient(ne::Color::GRAY);
    light->setAttenuationFactor(ne::LAT_LINEAR, 0.002f);
    light->setPosition(ne::Vector3(0.0f, 0.0f, 500.0f));

    // Set fog
    fog.setColor(ne::Color(0.5f, 0.0f, 1.0f));

    // Setup Skeleton
    human = pEngine->getSkeletonManager()->create("Rockie", "Rockie.skeleton", true);
    std::cout << human->getName() << std::endl;
    
    rootJoint = human->createJoint();
    rootJoint->setPosition(ne::Vector3(0.0f, 10.0f, 0.0f));
    std::cout << rootJoint->getObjectName() << std::endl;

    childJoint = rootJoint->createChild(1, "childJoint", ne::Vector3(0.0f, 0.0f, 10.0f));
    childJoint->yaw(ne::Math::HALF_PI);
    std::cout << childJoint->getObjectName() << std::endl;

    leafJoint = childJoint->createChild(2, "leafJoint", ne::Vector3(0.0f, 5.0f, 0.0f));
    std::cout << leafJoint->getObjectName() << std::endl;

    human->setBinding();
    human->deriveRootJoint();

    animationGroup = pEngine->getAnimationManager()->create("Human AnimationGroup", "Rockie.animationGroup", true);
    animation = animationGroup->create("Transform Animation", "Rockie.animation");
}

void keyboard(unsigned char key, int x, int y)
{
    if (key == 'w' || key == 'W')
    {
        camera->translate(ne::Vector3::NEGATIVE_UNIT_Z, ne::TS_LOCAL);
    }
    else if (key == 's' || key == 'S')
    {
        camera->translate(ne::Vector3::UNIT_Z, ne::TS_LOCAL);
    }
    else if (key == 'a' || key == 'A')
    {
        camera->translate(ne::Vector3::NEGATIVE_UNIT_X, ne::TS_LOCAL);
    }
    else if (key == 'd' || key == 'D')
    {
        camera->translate(ne::Vector3::UNIT_X, ne::TS_LOCAL);
    }
    else if (key == 'o' || key == 'O')
    {
        camera->translate(ne::Vector3::NEGATIVE_UNIT_Y, ne::TS_PARENT);
    }
    else if (key == 'u' || key == 'U')
    {
        camera->translate(ne::Vector3::UNIT_Y, ne::TS_PARENT);
    }
    else if (key == 'q' || key == 'Q')
    {
        camera->yaw(ne::Math::PI / 36);
    }
    else if (key == 'e' || key == 'E')
    {
        camera->yaw(- ne::Math::PI / 36);
    }
    else if (key == 'i' || key == 'I')
    {
        camera->pitch(ne::Math::PI / 36);
    }
    else if (key == 'k' || key == 'K')
    {
        camera->pitch(- ne::Math::PI / 36);
    }
    else if (key == 'j' || key == 'J')
    {
        camera->roll(ne::Math::PI / 36);
    }
    else if (key == 'y' || key == 'Y')
    {
        fov += 0.05;
        resize(Width, Height);
    }
    else if (key == 'h' || key == 'H')
    {
        fov -= 0.05;
        resize(Width, Height);
    }
    else if (key == 'l' || key == 'L')
    {
        camera->roll(- ne::Math::PI / 36);
    }
    else if (key == 'n' || key == 'N')
    {
        if (!playAnimation)
        {
            animationTimer.reset();
            playAnimation = true;
        }
    }
    else if (key == 'b' || key == 'B')
    {
        if (leafJoint)
        {
            if (leafJoint->getParent() == 0)
            {
                childJoint->addChild(leafJoint);
            }
            else
            {
                childJoint->removeChild(leafJoint);
            }
            human->deriveRootJoint();
        }
    }
    else if (key == 'r' || key == 'R')
    {
        if (leafJoint == 0)
        {
            human->reset();
            leafJoint = childJoint->createChild(2, "leafJoint", ne::Vector3(0.0f, 5.0f, 0.0f));
            human->setBinding();
            if (leafJoint)
            {
                std::cout << "Create leafJoint\n";
            }
        }
        else
        {
            if (childJoint->destroyChild(leafJoint))
            {
                leafJoint = 0;
                animation->removeNodeTrack(2);
                std::cout << "Remove leafJoint\n";
            }
        }
        human->deriveRootJoint();
    }
    else if (key == 'p' || key == 'P')
    {
        std::cout << camera << std::endl;
        std::cout << "Position : " << camera->getPosition() << std::endl;
        std::cout << "Direction : " << camera->getDirection() << std::endl;
        std::cout << "Orientation : " << camera->getOrientation() << std::endl;
    }
    else if (key == 'v' || key == 'V')
    {
        human->download();
        animationGroup->download();
    }
    else if (key == 0x1b)
    {
        exit(0);
    }
}

void createKeyFrame()
{
    human->update();
    ne::real nowTime = 0.001f * animationTimer.getMillisecondsCPU();
    ne::NodeAnimationTrackPtr track;
    ne::TransformKeyFrame *keyFrame;
    ne::Quaternion rotation;

    track = animation->getNodeTrack(0);
    if (track.notNull())
    {
        keyFrame = track->createTransformKeyFrame(nowTime);
        rotation = rootJoint->getInitialOrientation().getRotationRelative(rootJoint->getOrientation());
        keyFrame->setRotate(rotation);
    }

    track = animation->getNodeTrack(1);
    if (track.notNull())
    {
        keyFrame = track->createTransformKeyFrame(nowTime);
        rotation = childJoint->getInitialOrientation().getRotationRelative(childJoint->getOrientation());
        keyFrame->setRotate(rotation);
    }

    track = animation->getNodeTrack(2);
    if (track.notNull())
    {
        keyFrame = track->createTransformKeyFrame(nowTime);
        if (leafJoint)
        {
            rotation = leafJoint->getInitialOrientation().getRotationRelative(leafJoint->getOrientation());
        }
        keyFrame->setRotate(rotation);
    }
}

void mouse(int button, int state, int x, int y)
{
    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    {
        mouseX = x;
        mouseY = y;
        mouseLeftPressed = true;
    }
    if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
    {
        mouseX = x;
        mouseY = y;
        mouseRightPressed = true;
    }

    if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
    {
        mouseLeftPressed = false;
    }
    if (button == GLUT_RIGHT_BUTTON && state == GLUT_UP)
    {
        mouseRightPressed = false;
    }

    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    {
        animation->removeAll();
        animation->createNodeTrack(0, rootJoint);
        animation->createNodeTrack(1, childJoint);
        animation->createNodeTrack(2, leafJoint);
        animationTimer.reset();
        human->reset();
    }

    if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
    {
        ne::real nowTime = 0.001f * animationTimer.getMillisecondsCPU();
        animation->setLength(nowTime);
        animation->optimise();
        ne::NodeAnimationTrackPtr track = animation->getNodeTrack(0);
        if (track.notNull())
        {
            std::cout << "KeyFramsCount: " << track->getKeyFramesCount() << std::endl;
        }
    }
}

void motion(int x, int y)
{
    if (mouseLeftPressed)
    {
        float a = (float)4.0f * (x - mouseX) / Width;
        float b = (float)4.0f * (y - mouseY) / Height;
        rootJoint->yaw(a);
        rootJoint->pitch(b);
    }
    if (mouseRightPressed)
    {
        float a = (float)4.0f * (x - mouseX) / Width;
        float b = (float)4.0f * (y - mouseY) / Height;
        childJoint->yaw(a);
        childJoint->pitch(b);
    }

    if (mouseLeftPressed || mouseRightPressed)
    {
        createKeyFrame();
    }
    mouseX = x;
    mouseY = y;
}

void loop(void)
{
    // Update the Skeleton
    if (playAnimation)
    {
        ne::real nowTime = 0.001f * animationTimer.getMillisecondsCPU();
        human->reset();
        animation->apply(nowTime);
        if (nowTime > animation->getLength())
        {
            playAnimation = false;
        }
    }
    human->update();
    redraw();
}

void redraw(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    const ne::Matrix4 &viewMatrix = camera->getViewMatrix();
    glLoadMatrixf(viewMatrix.ptr());

    glEnable(GL_LIGHTING);

    glPushMatrix();
        //light.setPosition(camera->getPosition());
        //light.setOrientation(camera->getOrientation());
        light->useLight();
        //glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);

        ne::RenderSystem::GetSingleton().setFog(&fog);

        // Use Normal method
        glBegin(GL_QUADS);
            // Render ground
            glColor4f(0.3f, 0.6f, 0.0f, 1.0f);
            glNormal3f(0.0f, 1.0f, 0.0f);
            glVertex3fv(ne::Vector3(-1000.0f, 0.0f, 1000.0f).ptr());
            glVertex3fv(ne::Vector3(1000.0f, 0.0f, 1000.0f).ptr());
            glVertex3fv(ne::Vector3(1000.0f, 0.0f, -1000.0f).ptr());
            glVertex3fv(ne::Vector3(-1000.0f, 0.0f, -1000.0f).ptr());

            // Render wall
            glColor4fv((1.0f * ne::Color::WHITE).ptr());
            glNormal3f(0.0f, 0.0f, 1.0f);
            glVertex3fv(ne::Vector3(-500.0f, 0.0f, -500.0f).ptr());
            glVertex3fv(ne::Vector3(500.0f, 0.0f, -500.0f).ptr());
            glVertex3fv(ne::Vector3(500.0f, 1000.0f, -500.0f).ptr());
            glVertex3fv(ne::Vector3(-500.0f, 1000.0f, -500.0f).ptr());

            // Left wall
            glNormal3f(1.0f, 0.0f, 0.0f);
            glVertex3fv(ne::Vector3(-500.0f, 0.0f, 500.0f).ptr());
            glVertex3fv(ne::Vector3(-500.0f, 0.0f, -500.0f).ptr());
            glVertex3fv(ne::Vector3(-500.0f, 1000.0f, -500.0f).ptr());
            glVertex3fv(ne::Vector3(-500.0f, 1000.0f, 500.0f).ptr());

            // Right wall
            glNormal3f(-1.0f, 0.0f, 0.0f);
            glVertex3fv(ne::Vector3(500.0f, 0.0f, -500.0f).ptr());
            glVertex3fv(ne::Vector3(500.0f, 0.0f, 500.0f).ptr());
            glVertex3fv(ne::Vector3(500.0f, 1000.0f, 500.0f).ptr());
            glVertex3fv(ne::Vector3(500.0f, 1000.0f, -500.0f).ptr());

            // Render sky
            glColor4f(0.0f, 0.5f, 1.0f, 1.0f);
            glNormal3f(0.0f, -1.0f, 0.0f);
            glVertex3fv(ne::Vector3(-1000.0f, 1000.0f, 1000.0f).ptr());
            glVertex3fv(ne::Vector3(-1000.0f, 1000.0f, -1000.0f).ptr());
            glVertex3fv(ne::Vector3(1000.0f, 1000.0f, -1000.0f).ptr());
            glVertex3fv(ne::Vector3(1000.0f, 1000.0f, 1000.0f).ptr());
        glEnd();

        // Render rootJoint
        glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
        glPushMatrix();
            glMultMatrixf(rootJoint->getTransformMatrix().ptr());
            glutSolidSphere(1.0f, 36, 18);
        glPopMatrix();

        // Render childJoint
        glColor4f(1.0f, 0.5f, 0.0f, 1.0f);
        glPushMatrix();
            glMultMatrixf(childJoint->getTransformMatrix().ptr());
            glutSolidSphere(1.0f, 36, 18);
        glPopMatrix();

        // Render leafJoint
        if (leafJoint)
        {
            glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
            glPushMatrix();
                glMultMatrixf(leafJoint->getTransformMatrix().ptr());
                glutSolidSphere(1.0f, 36, 18);
            glPopMatrix();
        }

        ne::Matrix4 offsetM;
        // Render childJoint mesh
        glColor4f(0.0f, 0.5f, 1.0f, 1.0f);
        glPushMatrix();
            childJoint->getOffsetTransformMatrix(offsetM);
            glMultMatrixf(offsetM.ptr());
            glTranslatef(2.0f, 15.0f, 10.0f);
            glutSolidCube(1.0f);
        glPopMatrix();

    glPopMatrix();
   
    glDisable(GL_LIGHTING);

    drawFPS();

    glutSwapBuffers();
    glutPostRedisplay();
}

void resize(int width, int height)
{
    /* Save the new width and height */
    Width  = width;
    Height = height;

    /* Reset the viewport->.. */
    glViewport(0, 0, width, height);
    viewport->setPosition(0, 0);
    viewport->resize(width, height);

    glMatrixMode(GL_PROJECTION);
    const ne::Matrix4 &projectionMatrix = camera->getProjectionMatrix();
    glLoadMatrixf(projectionMatrix.ptr());

    glMatrixMode(GL_MODELVIEW);
}

void drawFPS()
{
    //Set matrices for ortho
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0.0f, viewport->getWidth(),
            0.0f, viewport->getHeight(),
            1.0f, -1.0f);

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	//Print text
    ne::FontPtr font = pEngine->getFont();
    ne::Vector3 pos(Width - 50.0f, 10.0f, 0.0f);
    float w = font->getFontWidth();
    float h = font->getFontHeight();

    char fpsString[32];
    ne::uint32_t interval = timer.getIntervalMilliseconds();
	sprintf(fpsString, "%.2f", 1000.0f / interval);

    font->renderText2D(fpsString, pos, 6.0f*w, h, ne::Color(0.8f, 1.0f, 0.0f, 1.0f));

	//reset matrices
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

